package errno

import (
	"encoding/json"
	"fmt"

	"gitee.com/krio/helper/utils"
	"github.com/gogo/status"
)

type errStruct struct {
	HTTPStatus int         `json:"-"`                     // http状态码
	Code       int32       `json:"code"`                  // 自定义状态码
	Message    string      `json:"message"`               // 响应描述
	SubMessage string      `json:"sub_message,omitempty"` // 响应提示
	Data       interface{} `json:"data,omitempty"`        // 响应数据
}

var errNos = make(map[int32]IErr)

func newErrNo(httpStatus int, code int32, message, prompt string, data interface{}) *errStruct {
	return &errStruct{
		HTTPStatus: httpStatus,
		Code:       code,
		Message:    message,
		SubMessage: prompt,
		Data:       data,
	}
}

type IErr interface {
	Error() string
	GetHTTPStatus() int
	GetCode() int32
	GetMessage() string
	GetSubMessage() string
	GetData() interface{}

	WithMessage(message string) IErr
	WithSubMsg(message string) IErr
	WithData(data interface{}) IErr
	ToString() string
}

func register(httpStatus int, code int32, message, prompt string) IErr {
	if _, exist := errNos[code]; exist {
		panic(fmt.Sprintf("errno %d already exists", code))
	}
	e := newErrNo(httpStatus, code, message, prompt, nil)
	errNos[code] = e
	return newErrNo(httpStatus, code, message, prompt, nil)
}

func (en *errStruct) Error() string {
	return en.ToString()
}

func (en *errStruct) GetHTTPStatus() int {
	return en.HTTPStatus
}

func (en *errStruct) GetCode() int32 {
	return en.Code
}

func (en *errStruct) GetMessage() string {
	return en.Message
}

func (en *errStruct) GetSubMessage() string {
	return en.SubMessage
}

func (en *errStruct) GetData() interface{} {
	return en.Data
}

// WithMessage 自定义响应信息
func (en *errStruct) WithMessage(message string) IErr {
	return newErrNo(en.HTTPStatus, en.Code, message, en.SubMessage, en.Data)
}

// WithSubMsg  自定义响应提示
func (en *errStruct) WithSubMsg(prompt string) IErr {
	return newErrNo(en.HTTPStatus, en.Code, en.Message, prompt, en.Data)
}

// WithData 追加响应数据
func (en *errStruct) WithData(data interface{}) IErr {
	return newErrNo(en.HTTPStatus, en.Code, en.Message, en.SubMessage, data)
}

// ToString 返回 JSON 格式的错误详情
func (en *errStruct) ToString() string {
	err := &errStruct{
		HTTPStatus: en.HTTPStatus,
		Code:       en.Code,
		Message:    en.Message,
		SubMessage: en.SubMessage,
		Data:       en.Data,
	}
	raw, _ := json.Marshal(err)

	return string(raw)
}

// Parse 解析错误
func Parse(err interface{}) IErr {
	if err == nil {
		return nil
	}
	switch e := err.(type) {
	case IErr:
		return e
	case error:
		// 获取gRPC的错误code
		if s, ok := status.FromError(e); ok {
			en := GetErrNo(int32(s.Code()))
			newEn := utils.Clone(en)
			return newEn.(IErr)
		}
		return Unknown.WithMessage(e.Error())
	default:
		return Unknown
	}
}

// GetErrNo 通过code获取错误
func GetErrNo(code int32) IErr {
	if en, exist := errNos[code]; exist {
		return en
	}
	return Unknown
}

// Is 判断两个错误是否是相同错误
func Is(err1 IErr, err2 IErr) bool {
	return err1.GetCode() == err2.GetCode()
}
